package com.jdy.utils;

import java.lang.reflect.Array;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * Spring 框架学习
 * <p>
 * Description: spring-mvc
 * <p>
 * Created by 49897 on 2020/4/4 16:21
 */
public class ArrayUtils {
    private static final String ARRAY_START = "{";
    private static final String ARRAY_END = "}";
    private static final String ARRAY_ELEMENT_SEPARATOR = ", ";
    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

    /**
     * 检查数组是否为空
     * 如果数组没有任何元素也将会判定为{@code true}
     *
     * @param array 数组
     * @param <T>   数组的数据类型
     * @return {@code true} 数组不为空
     */
    public static <T> boolean isEmpty(final T[] array) {
        return array == null || array.length == 0;
    }

    public static <T> boolean isNotEmpty(final T[] array) {
        return !isEmpty(array);
    }

    /**
     * 判断{@code Object}对象是否为数组对象
     * 如果{@code Object}对象为空，则会被认为不是数组对象
     *
     * @param object 对象
     * @return {@code true} 是数组对象
     */
    public static boolean isArray(Object object) {
        return !isNotArray(object);
    }

    public static boolean isNotArray(Object object) {
        return null == object || !object.getClass().isArray();
    }

    /**
     * 判断数组中是否包含指定元素
     *
     * @param array   数组对象
     * @param element 指定元素
     * @param <T>     数组数据类型
     * @return {@code true}数组包含指定元素
     * @see #hasElement(Object[], Predicate)
     */
    public static <T> boolean hasElement(T[] array, final T element) {
        return hasElement(array, item -> Objects.deepEquals(item, element));
    }


    /**
     * 对数组按照指定规则进行检查，
     *
     * @param array     数组对象
     * @param predicate 判定规则
     * @param <T>       数组数据类型
     * @return 如果满足判定规则，将返回{@code true}
     */
    public static <T> boolean hasElement(T[] array, Predicate<T> predicate) {
        if (isEmpty(array)) return false;
        Objects.requireNonNull(predicate, "Predicate must not be null!");
        for (T element : array)
            if (predicate.test(element)) return true;
        return false;
    }

    /**
     * 检查数组中是否包含重复的元素
     *
     * @param elements 数组对象
     * @param <T>      数组数据类型
     * @return {@code true} 数组包含重复元素
     * @see #hasRepeatElement(Object[], boolean)
     */
    @SafeVarargs
    public static <T> boolean hasRepeatElement(T... elements) {
        return hasRepeatElement(elements, true);
    }

    /**
     * 检查数组中是否包含重复的元素
     * <p>
     * 此方法基于{@link Set#contains(Object)}方法实现
     *
     * @param elements   数组对象
     * @param ignoreNull 是否忽略空对象
     * @param <T>        数组数据类型
     * @return {@code true} 数组包含重复元素
     * @see Set#contains(Object)
     */
    public static <T> boolean hasRepeatElement(T[] elements, boolean ignoreNull) {
        Set<T> set = new TreeSet<>();
        for (T element : elements) {
            //如果含有相同元素，直接返回true
            if (set.contains(element)) return true;

            //如果忽略空元素，则直接跳过空元素即可
            if (element == null && ignoreNull)
                continue;
            //加入set集合
            set.add(element);
        }
        return false;
    }

    /**
     * 用指定元素来替代数组指定位置上的元素
     * <p>
     * 需要注意的是：如果数组对象为空或者指定位置超出数组的范围，将返回原数组对象
     *
     * @param array   数组对象
     * @param element 指定元素
     * @param index   指定位置
     * @param <T>     数组数据类型
     * @return 替代后的阿数组
     */
    public static <T> T[] replaceElements(T[] array, T element, int index) {
        if (isEmpty(array) || index >= array.length) {
            return array;
        }
        array[index] = element;
        return array;
    }

    /**
     * 用指定元素替换数组中的所有元素
     * <p>
     * 此方法的替换是调用{@link Arrays#fill(Object[], Object)}方法实现的
     *
     * @param array   数组对象
     * @param element 指定元素
     * @param <T>     数组数据类型
     * @return 替换后的数组
     * @see Arrays#fill(Object[], Object)
     */
    public static <T> T[] replaceAllElements(T[] array, T element) {
        if (isEmpty(array)) {
            return array;
        }
        Arrays.fill(array, element);
        return array;
    }

    /**
     * 将数组转换成字符串
     * <p>
     * 备注：这里元素与元素的分割符默认为{@link #ARRAY_ELEMENT_SEPARATOR}，
     * 并且带有前缀({@link #ARRAY_START})和后缀({@link #ARRAY_END}).
     * <pre>
     * 例如：数组 int array = {1，2， 3， 4} 将会被转换为 “{1，2，3，4}”
     *       而 int array = null 将会被转换成“{}”
     * </pre>
     *
     * @param array 数组对象
     * @param <T>   数组数据类型
     * @return 转换后的字符串
     * @see #toString(Object[], String)
     */
    public static <T> String toString(T[] array) {
        return toString(array, ARRAY_ELEMENT_SEPARATOR);
    }

    /**
     * 将数组按照指定分隔符{@code delimiter}转换成字符串
     *
     * @param array     数组对象
     * @param delimiter 分隔符
     * @param <T>       数组数据类型
     * @return 转换后的字符串
     * @see #toString(Object[], String, String, String)
     */
    public static <T> String toString(T[] array, String delimiter) {
        return toString(array, ARRAY_START, delimiter, ARRAY_END);
    }

    /**
     * 将数组按照指定前缀、分隔符和后最转换成字符串
     * <p>
     * 此方法基于{@link StringJoiner}实现， 通过{@link StringJoiner#add(CharSequence)}方法添加元素，
     * 因此数组元素将会被{@link String#valueOf(Object)}先转换成String对象，然后在添加至{@link StringJoiner}中
     *
     * @param array     数组对象
     * @param prefix    前缀
     * @param delimiter 分隔符
     * @param suffix    后缀
     * @param <T>       数组数据类型
     * @return 转换后的字符串
     * @see StringJoiner
     * @see StringJoiner#add(CharSequence)
     * @see String#valueOf(Object)
     */
    public static <T> String toString(T[] array, String prefix, String delimiter, String suffix) {
        if (array == null) return "";

        if (array.length == 0) return prefix + suffix;

        StringJoiner joiner = new StringJoiner(delimiter, prefix, suffix);
        for (T element : array) {
            joiner.add(String.valueOf(element));
        }

        return joiner.toString();
    }

    public static String[] reset(String[] array) {
        return reset(array, new ArrayFunction<String, String>() {
            @Override
            public String[] newArray(int length) {
                return new String[length];
            }

            @Override
            public String apply(String element) {
                if (element == null)
                    return null;
                return element.trim();
            }
        });
    }

    public static <T, R> R[] reset(T[] array, ArrayFunction<T, R> function) {
        Objects.requireNonNull(function, "ArrayFunction must not be null!");
        if (array == null) return null;

        R[] result = function.newArray(array.length);

        for (int i = 0; i < result.length; i++) {
            result[i] = function.apply(array[i]);
        }

        return result;
    }

    public static Object[] toObjectArray(Object source) {
        if (source instanceof Object[]) return (Object[]) source;

        if (source == null) return EMPTY_OBJECT_ARRAY;

        if (isNotArray(source))
            throw new IllegalArgumentException("Source is not an array: " + source);

        int length = Array.getLength(source);
        if (length == 0) {
            return EMPTY_OBJECT_ARRAY;
        }
        Class<?> wrapperType = Array.get(source, 0).getClass();
        Object[] newArray = (Object[]) Array.newInstance(wrapperType, length);
        for (int i = 0; i < length; i++) {
            newArray[i] = Array.get(source, i);
        }
        return newArray;
    }

    interface ArrayFunction<T, R> extends Function<T, R> {

        R[] newArray(int length);

    }
}
